﻿namespace CloudNinja.Metering.Common
{
    using System;
    using System.Collections.Generic;
    using System.Data.Services;
    using System.Data.Services.Client;
    using System.Linq;
    using System.Net;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Xml.Linq;

    using CloudNinja.Metering.Authentication;
    using CloudNinja.Metering.Data;
    using CloudNinja.Metering.Data.Models;

    public class MeteringRepositoryClient : DataServiceContext, IMeteringRepository
    {
        private readonly string userId = string.Empty;
        private readonly IHmacHelper hmacHelper;
        private DataServiceQuery<MeterData> meterData;

        public MeteringRepositoryClient(string userId, Uri serviceRoot, IHmacHelper hmacHelper)
            : base(serviceRoot)
        {
            this.userId = userId;
            this.hmacHelper = hmacHelper;

            this.SendingRequest += (s, a) =>
                {
                    a.Request.Headers.Set(HeaderNamesForHmac.DateHeader, DateTime.Now.ToUniversalTime().ToString("r"));
                    a.RequestHeaders.Set(HeaderNamesForHmac.UsernameHeaderName, Convert.ToBase64String(Encoding.ASCII.GetBytes(userId)));
                    a.RequestHeaders.Set(
                        HttpRequestHeader.Authorization,
                        (new AuthenticationHeaderValue(
                            HeaderNamesForHmac.AuthorizationScheme,
                            hmacHelper.CreateAuthenticationToken(this.userId, a.Request))).ToString());
                };
            this.WritingEntity += this.OnWritingEntity;
        }

        public DataServiceQuery<MeterData> MeterData
        {
            get
            {
                if ((this.meterData == null))
                {
                    this.meterData = base.CreateQuery<MeterData>("MeterData");
                }
                return this.meterData;
            }
        }

        protected Type ResolveTypeFromName(string typeName)
        {
            if (typeName.StartsWith("CloudNinja.Metering.Data", StringComparison.Ordinal))
            {
                return this.GetType().Assembly.GetType(string.Concat("CloudNinja.Metering.Common.CnmbDataService", typeName.Substring(24)), false);
            }
            return null;
        }

        protected string ResolveNameFromType(Type clientType)
        {
            if (clientType.Namespace.Equals("CloudNinja.Metering.Common.CnmbDataService", StringComparison.Ordinal))
            {
                return string.Concat("CloudNinja.Metering.Data.", clientType.Name);
            }
            return null;
        }

        // we look for IgnorePropertiesAttribute and remove properties we don't want 
        // to serialize from the result
        private void OnWritingEntity(object sender, ReadingWritingEntityEventArgs args)
        {
            var entityType = args.Entity.GetType();
            var ignorePropertiesAttribute = entityType.GetCustomAttributes(true).FirstOrDefault(x => x is IgnorePropertiesAttribute) as IgnorePropertiesAttribute;
            if (ignorePropertiesAttribute == null)
            {
                return;
            }

            var xnEntityProperties = XName.Get("properties", args.Data.GetNamespaceOfPrefix("m").NamespaceName);
            XElement xePayload = null;

            foreach (var property in args.Entity.GetType().GetProperties().Where(property => ignorePropertiesAttribute.PropertyNames.Contains(property.Name)))
            {
                if (xePayload == null)
                {
                    xePayload = args.Data.Descendants().First(xe => xe.Name == xnEntityProperties);
                }

                // The XName of the property we are going to remove from the payload
                var xnProperty = XName.Get(property.Name, args.Data.GetNamespaceOfPrefix("d").NamespaceName);

                // Get the Property of the entity  you don't want sent to the server
                for (int index   = 0; index < xePayload.Descendants(xnProperty).Count(); index++)
                {
                    var xeRemoveThisProperty = xePayload.Descendants(xnProperty).ToList()[index];

                    // Remove this property from the Payload sent to the server 
                    xeRemoveThisProperty.Remove();
                }
            }
        }

        public void Write(string meterTypeId, string tenantId, double value, DateTimeOffset timeStamp, int count)
        {
            var meterDataItem = new MeterData()
            {
                MeterTypeId = meterTypeId,
                TenantId = tenantId,
                Value = value,
                TimePeriod = timeStamp.UtcDateTime,
                Count = count
            };

            this.Write(meterDataItem);
        }

        public void Write(MeterData dataPoint)
        {
            this.AddObject("MeterDataRepository", dataPoint);
            this.SaveChanges();
        }

        public void Write(IEnumerable<MeterData> dataPoints)
        {
            foreach (var dataPoint in dataPoints)
            {
                this.AddObject("MeterDataRepository", dataPoint);
            }
            this.SaveChanges();
        }
    }
}